﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using Passwords.DAL;

namespace Passwords.Biz
{
    public class Context : IDisposable
    {
        private List<Category> categories;
        private List<Password> passwords;
        private List<PasswordCategory> passwordCategories;
        private List<CategoryLabel> categoryLabels;
        private List<PasswordLabel> labels;

        private IDalContext context;

        public static bool CheckPassword(string password)
        {
            return DalContext.CheckPassword(password);
        }


        public static Context Current { get; private set; }

        public static Context Create(string password)
        {
            if (Current != null) Current.Dispose();
            Current = new Context(password);
            return Current;
        }

        private Context(string password)
            : base()
        {
            context = new DalContext(password);
            InitializeContext();
        }

        public void Dispose()
        {
            context.Dispose();
        }

        private void InitializeContext()
        {
            categories = context.GetCategories().ToList();
            passwords = context.GetPasswords().ToList();
            passwordCategories = context.GetPasswordCategories().ToList();
            categoryLabels = context.GetCategoryLabels().ToList();
            labels = context.GetLabels().ToList();
        }

        public IList<PasswordLabel> Labels { get { return labels; } }
        public IList<Category> Categories { get { return categories; } }
        public IList<Password> Passwords { get { return passwords; } }
        public IList<Password> Favorites { get { return GetFaves(); } }

        private IList<Password> GetFaves()
        {
            var result = from p in passwords where p.IsFavorite == true orderby p.Name select p;
            return result.ToList();
        }

        public IList<Category> GetCategories(Password password)
        {
            if (password == null) throw new ArgumentNullException("password");
            var result = from c in categories where passwordCategories.Any(pc => pc.CategoryId == c.Id && pc.PasswordId == password.Id) select c;

            return result.ToList();
        }

        public IList<Password> GetPasswords(Category category)
        {
            if (category == null) throw new ArgumentNullException("category");
            var result = from p in passwords where passwordCategories.Any(pc => pc.PasswordId == p.Id && pc.CategoryId == category.Id) select p;
            return result.ToList();
        }

        public IList<PasswordLabel> GetLabels(Category category)
        {
            if (category == null) throw new ArgumentNullException("category");
            var result = from l in labels where categoryLabels.Any(cl => cl.CategoryId == category.Id && cl.LabelId == l.Id) select l;
            return result.ToArray();
        }

        public PasswordLabel GetLabel(int id)
        {
            return labels.FirstOrDefault(l => l.Id == id);
        }

        public void Save(Password password)
        {
            if (password == null) throw new ArgumentNullException("password");
            switch (password.EntityState)
            {
                case EntityState.Modified:
                    context.UpdatePassword(password);
                    UpdateLocalData();
                    OnPasswordSaved(password);
                    break;

                case EntityState.New:
                    context.AddPassword(password);
                    this.passwords.Add(password);
                    UpdateLocalData();
                    OnPasswordAdded(password);
                    OnPasswordSaved(password);
                    break;
            }
        }


        private void UpdateLocalData()
        {
            passwordCategories = context.GetPasswordCategories().ToList();
            passwords.Sort((a, b) => string.Compare(a.Name, b.Name, true));
        }

        public void Delete(Password password)
        {
            if (password == null) throw new ArgumentNullException("password");
            context.DeletePassword(password);
            OnPasswordDeleted(password);
        }

        public IList<PasswordItem> GetEditItems(Password password)
        {
            var categories = password.Categories.Select(c => c.Id);
            var lIds = from cl in categoryLabels where password.Categories.Any(c => c.Id == cl.CategoryId) select cl.LabelId;
            var items = from l in this.labels where lIds.Contains(l.Id) && !password.Items.Any(i => i.LabelId == l.Id) select new PasswordItem(password, l);
            var result = items.Union(password.Items).OrderBy(pi => pi.DisplayIndex).ThenBy(pi => pi.Name);
            var list = result.ToList();
            //list.Insert(0, new TitlePasswordItem(password));
            // list.Add(new CategoryPasswordItem(password));
            return list;
        }

        public IEnumerable<CategorizedPassword> GetCategorizedPasswords()
        {
            var result = from c in categories from p in c.Passwords orderby c.Name, p.Name select new CategorizedPassword(c, p);
            return result;

        }

        public static void CloseCurrent()
        {
            if (Current != null)
            {
                Current.Dispose();
                Current = null;
            }
        }

        public void Save(Category category)
        {
            switch (category.EntityState)
            {
                case EntityState.New:
                    context.AddCategory(category);
                    categories.Add(category);
                    break;

                case EntityState.Modified:
                    context.UpdateCategory(category);
                    break;
            }
            ModifyCategoryLabels(category);
            categories.Sort((a, b) => string.Compare(a.Name, b.Name, true));
        }

        private void ModifyCategoryLabels(Category category)
        {
            var cls = from cl in categoryLabels where cl.CategoryId != category.Id select cl;
            cls = cls.Union(from l in category.Labels select new CategoryLabel(category.Id, l.Id));
            categoryLabels = cls.ToList();
        }

        public void Delete(Category category)
        {
            context.DeleteCategory(category);
            OnCategoryDeleted(category);
            categoryLabels.RemoveAll(c => c.CategoryId == category.Id);
            passwordCategories.RemoveAll(p => p.CategoryId == category.Id);
            Categories.Remove(category);
        }

        protected void OnPasswordSaved(Password p)
        {
            if (PasswordSaved != null) PasswordSaved(this, new PasswordEventArgs(p));
        }

        protected void OnPasswordAdded(Password p)
        {
            if (PasswordAdded != null) PasswordAdded(this, new PasswordEventArgs(p));
        }

        protected void OnPasswordDeleted(Password p)
        {
            if (PasswordDeleted != null) PasswordDeleted(this, new PasswordEventArgs(p));
        }

        protected void OnCategoryDeleted(Category c)
        {
            if (CategoryDeleted != null) CategoryDeleted(this, new CategoryEventArgs(c));
        }


        public event EventHandler<PasswordEventArgs> PasswordSaved;
        public event EventHandler<PasswordEventArgs> PasswordAdded;
        public event EventHandler<PasswordEventArgs> PasswordDeleted;
        public event EventHandler<CategoryEventArgs> CategoryDeleted;

        public void DisplayIndexChanged(PasswordLabel label, int newIndex)
        {
            if (label == null) throw new ArgumentNullException("label");

            labels.Remove(label);
            newIndex = Math.Min(labels.Count, newIndex);
            labels.Insert(newIndex, label);
            for (int i = 0; i < labels.Count; i++)
            {
                labels[i].SetDisplayIndexInternal(i);
            }
        }

        public void Save(PasswordLabel label)
        {
            if (label == null) throw new ArgumentNullException("label");

            switch (label.EntityState)
            {
                case EntityState.New:
                    context.InsertLabel(label);
                    break;

                case EntityState.Modified:
                    if (!label.IsIndexModified) context.UpdateLabel(label);
                    break;

            }
            if (label.IsIndexModified || label.EntityState == EntityState.New)
            {
                DisplayIndexChanged(label, label.DisplayIndex);
                context.UpdateLabels(labels);
            }

        }

        internal int GetAssociatedPasswords(PasswordLabel label)
        {
            if (label == null) throw new ArgumentNullException("label");
            var all = from p in this.passwords from i in p.Items where i.LabelId == label.Id select p;
            return all.Count();
        }

        internal void Delete(PasswordLabel label)
        {
            if (label == null) throw new ArgumentNullException("label");
            context.DeleteLabel(label);
            categoryLabels.RemoveAll(c => c.LabelId == label.Id);
            labels.Remove(label);
        }
    }
}
